﻿using EasyXaf.LowCode.WorkflowEditors.UndoManagers.Commands;
using EasyXaf.LowCode.WorkflowEditors.UndoManagers.States;
using EasyXaf.LowCode.WorkflowEditors.UndoManagers.Transactions;

namespace EasyXaf.LowCode.WorkflowEditors.UndoManagers;

public sealed class UndoManager : IUndoManager, ITransactionManager, IStateHost
{
    private readonly ITransactionFactory _transactionFactory;
    private readonly Stack<ICommandTransaction> _undoHistory = new();
    private readonly Stack<ICommandTransaction> _redoHistory = new();
    private readonly Stack<ICommandTransaction> _openTransactions = new();

    private int _lock;
    private UndoRedoState _state;

    public event EventHandler StateChanged;

    public UndoManager()
        : this(new TransactionFactory())
    {
    }

    public UndoManager(ITransactionFactory transactionFactory)
    {
        _transactionFactory = transactionFactory ?? throw new ArgumentNullException(nameof(transactionFactory));
        _state = UndoRedoState.Idle;
    }

    UndoRedoState IStateHost.State
    {
        get => _state;
        set => _state = value;
    }

    private void OnStateChanged()
    {
        StateChanged?.Invoke(this, EventArgs.Empty);
    }

    public bool IsUndoing
    {
        get => _state == UndoRedoState.Undoing;
    }

    public bool IsRedoing
    {
        get => _state == UndoRedoState.Redoing;
    }

    public bool IsLocked
    {
        get => _lock > 0;
    }

    public bool CanUndo
    {
        get => _undoHistory.Any() || _openTransactions.Any();
    }

    public bool CanRedo
    {
        get => _redoHistory.Any();
    }

    public void Undo()
    {
        if (_openTransactions.Any())
        {
            CommitTransactions();
        }

        if (!_undoHistory.Any())
        {
            throw new InvalidOperationException("没有可撤消的操作");
        }

        using (new StateSwitcher(this, UndoRedoState.Undoing))
        {
            var command = _undoHistory.Pop();
            using (InnerCreateTransaction(command.ActionName))
            {
                InvokeCommand(command);
                OnStateChanged();
            }
        }
    }

    public void Redo()
    {
        if (!_redoHistory.Any())
        {
            throw new InvalidOperationException("没有可恢复的操作");
        }

        using (new StateSwitcher(this, UndoRedoState.Redoing))
        {
            var command = _redoHistory.Pop();
            using (InnerCreateTransaction(command.ActionName))
            {
                InvokeCommand(command);
                OnStateChanged();
            }
        }
    }

    public void Clear()
    {
        _redoHistory.Clear();
        _undoHistory.Clear();
        _openTransactions.Clear();
        _state = UndoRedoState.Idle;
    }

    public void BeginLock()
    {
        _lock++;
    }

    public void EndLock()
    {
        _lock--;
    }

    public ITransaction CreateTransaction(string actionName)
    {
        return InnerCreateTransaction(actionName);
    }

    public void CommitTransactions()
    {
        if (!_openTransactions.Any())
        {
            throw new InvalidOperationException("没有可提交的打开事务");
        }
        _openTransactions.First().Commit();
    }

    public void RollbackTransactions()
    {
        if (!_openTransactions.Any())
        {
            throw new InvalidOperationException("没有可回滚的打开事务");
        }
        _openTransactions.First().Rollback();
    }

    public void RegisterCommand(ICommand command)
    {
        if (command == null)
        {
            throw new ArgumentNullException(nameof(command));
        }

        if (_state != UndoRedoState.RollingBack)
        {
            var recordingTransaction = FetchRecordingTransaction();
            if (recordingTransaction != null)
            {
                recordingTransaction.RegisterCommand(command);
            }
            else
            {
                using var transaction = InnerCreateTransaction(string.Empty);
                transaction.RegisterCommand(command);
            }
        }
    }

    void ITransactionManager.CommitTransaction(ICommandTransaction transaction)
    {
        if (transaction == null)
        {
            throw new ArgumentNullException(nameof(transaction));
        }

        if (!_openTransactions.Contains(transaction))
        {
            throw new ArgumentException("找不到要提交的事务", nameof(transaction));
        }

        var currentState = _state == UndoRedoState.Idle ? UndoRedoState.Committing : _state;

        using (new StateSwitcher(this, currentState))
        {
            while (_openTransactions.Contains(transaction))
            {
                var toCommit = _openTransactions.Pop();
                if (toCommit.Any())
                {
                    if (toCommit.Equals(transaction) && !_openTransactions.Any())
                    {
                        (IsUndoing ? _redoHistory : _undoHistory).Push(transaction);
                    }
                    else
                    {
                        _openTransactions.Peek().RegisterCommand(toCommit);
                    }
                }
            }
        }

        if (!_openTransactions.Any())
        {
            if (currentState == UndoRedoState.Committing)
            {
                _redoHistory.Clear();
            }
        }
    }

    void ITransactionManager.RollbackTransaction(ICommandTransaction transaction)
    {
        if (transaction == null)
        {
            throw new ArgumentNullException(nameof(transaction));
        }

        if (!_openTransactions.Contains(transaction))
        {
            throw new ArgumentException("找不到要回滚的事务", nameof(transaction));
        }

        while (_openTransactions.Contains(transaction))
        {
            _openTransactions.Pop().Rollback();
        }
    }

    private ICommandTransaction InnerCreateTransaction(string commandName)
    {
        var transaction = _transactionFactory.CreateTransaction(this);
        transaction.ActionName = commandName;
        _openTransactions.Push(transaction);
        return transaction;
    }

    private ICommandTransaction FetchRecordingTransaction()
    {
        return _openTransactions.Count > 0 ? _openTransactions.Peek() : null;
    }

    private void InvokeCommand(ICommand command)
    {
        try
        {
            BeginLock();
            command.Invoke();
            EndLock();
        }
        catch (Exception e)
        {
            throw new ActionCommandException("执行命令时出现错误", e);
        }
    }

    public void Dispose()
    {
        Clear();
        StateChanged = null;
    }
}
